%==========================================================================
%=  This file is part of the Sound Restoration Project
%=  (c) Copyright Industrial Mathematics Institute
%=                University of South Carolina, Department of Mathematics
%=  ALL RIGHTS RESERVED
%=
%=  Author: Borislav Karaivanov
%==========================================================================

%==========================================================================
% List of the files on which this procedure depends:
%
% getBitDepthFromVideoObject.m
% patientlyKeepTryingToReadUntilErrorFree.m
%
% image processing toolbox
%
%==========================================================================

%==========================================================================
% The function "readAndCheckVideoFrames" reads a specified range of frames
% from a given video object and always checks if the read data is an array
% of the expected size. If requested, the read frames are converted to
% grayscale.
% INPUT: "videoObj" is a video object allowing to pull frames from a video
% file.
% "frameRange" is a single integer or a row vector of two integers
% specifying the index of a single frame or a range of frames to be read
% from the given video object.
% "doConvertToGray" is an optional boolean specifying whether the read
% frames are to be converted to grayscale before returned.
% "pausePeriod" is an optional parameter specifying the time to wait before
% another attempt to read the desired frames is made.
% "maxPauseTime" is an optional parameter specifying an upper bound on the 
% cumulative wait time allowed before the reading effort is abandoned.
% OUTPUT: "frameArr" is an array returning the read frames.
% "bitDepth" returns the number of bits used to encode each pixel value in
% any single color channel.
%==========================================================================
function [frameArr, bitDepth] = readAndCheckVideoFrames(videoObj, frameRange, ...
    doConvertToGray, pausePeriod, maxPauseTime)


fprintf(1, 'Using SAFE reading.\n');

if (nargin < 4)
    pausePeriod = 1;
end
if (nargin < 5)
    maxPauseTime = 10;
end
% Enable pausing.
pause on

% Handle the case when the function is called with a single frame index
% specified as a single number (rather than a row vector with two identical
% entries).
frameRange = [frameRange(1), frameRange(end)];

% Get the dimensions the read data is expected to have.
isMoreThanOneFrameRead = (frameRange(end) > frameRange(1));
if (isMoreThanOneFrameRead == false)
    expectedDimsArr = [videoObj.Height, videoObj.Width];
    expectedNumDims = 3;
    indArr = [1, 2];
else
    expectedDimsArr = [videoObj.Height, videoObj.Width, frameRange(2) - frameRange(1) + 1];
    expectedNumDims = 4;
    indArr = [1, 2, 4];
end

% Try to read in the desired frames.
frameArr = patientlyKeepTryingToReadUntilErrorFree(videoObj, frameRange, pausePeriod, maxPauseTime);

% Get the dimensions the read data actually has.
sizeArr = size(frameArr);

totalPauseTime = 0;
while ( (totalPauseTime < maxPauseTime)&& ...
        ( (numel(sizeArr) ~= expectedNumDims)||(nnz(expectedDimsArr ~= sizeArr(indArr)) > 0) ) )
    
    % Wait some time.
    pause(pausePeriod);
    % Keep track of the total time waited so far.
    totalPauseTime = totalPauseTime + pausePeriod;
    
    % Try to read in the desired frames again.
    frameArr = patientlyKeepTryingToReadUntilErrorFree(videoObj, frameRange, pausePeriod, maxPauseTime);
    % Get the dimensions the read data actually has.
    sizeArr = size(frameArr);
end

% Disable pausing.
pause off

% Check if the allowed wait time was completely used up. 
if (totalPauseTime > maxPauseTime)
    error('SoundRestoration:readAndCheckVideoFrames:FailedToReadTheDesiredFrames', ...
        ['The desired frame range [' num2str(frameRange(1)) ',' num2str(frameRange(end)) ...
        '] could not be read in '  num2str(totalPauseTime) 'seconds.']);
end

% Get the bit depth per pixel per color channel.
bitDepth = getBitDepthFromVideoObject(videoObj);

% Convert to grayscale images, if requested, and provided the read pixels
% have three or more components (like RGB).
if ( (nargin >= 3)&&(doConvertToGray == true)&&(ndims(frameArr) == (3 + isMoreThanOneFrameRead)) )
    if (isMoreThanOneFrameRead == true)
        % Get the dimensions of the array of read frames.
        sizeArr = size(frameArr);
        % Allocate memory for the grayscale frames.
        grayFrameArr = zeros(sizeArr([1, 2, 4]));
        % Convert the read frames to grayscale one at a time.
        for k = 1:sizeArr(4)
            grayFrameArr(:, :, k) = rgb2gray(frameArr(:, :, 1:3, k));
        end
        % Set the output.
        frameArr = grayFrameArr;
    else
        frameArr = rgb2gray(frameArr(:, :, 1:3));
    end
end

return;
% end of the function "readAndCheckVideoFrames"
